//
//  UploadManager.swift
//  SitPosition
//
//  Created by apple on 2019/2/10.
//  Copyright © 2019 apple. All rights reserved.
//

import Foundation


/**
 * 上传管理器
 * 由于实时视频采集所产生的图片量会比较大，都放在内存不太现实，所以上传模块做如下处理：
 * > 模块启动时先加载出所有未完成的上传任务，放入一个队列进行维护；
 * > 先将传入的Data根据key进行本地保存；
 * > 构造新的上传任务，追加到队列尾部，并任务状态为“未完成，并放入SQLite保存；
 * > 上传队列处理线程，逐一处理上传任务，处理完成后删除本地文件和SQLite记录；
 */
extension Notification.Name {
    // object: [String] 上传成功或失败的任务key(s)
    public static let uploadSuccess: Notification.Name = Notification.Name("uploadManager.success")
    public static let uploadFailure: Notification.Name = Notification.Name("uploadManager.failure")
}

class UploadManager {
    
    // 单例
    public static let manager: UploadManager = UploadManager()
    // 当前用户的ID
    public var currentUserId: String? = nil
    
    // 上传队列
    private let taskQueue: DispatchQueue = DispatchQueue(label: "UploadManager")
    private let pendingTasksLock: NSLock = NSLock()
    private var pendingTasks: [UploadTask] = []
    
    // 工作线程
    private var canExitWorkerThread: Bool = false
    private let workerThreadSemaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
    private lazy var workerThread: Thread = {
        return Thread(target: self, selector: #selector(_workerThread(_:)), object: nil)
    }()
    
    // 是否启动标志？
    private var isStarted: Bool = false
    // 七牛OSS
    private let qiniuOSSLock: NSLock = NSLock()
    private var qiniuOSS: QiniuBuckets? = nil
    
    // MARK: - Object lifecycle
    
    deinit {
        canExitWorkerThread = true
        workerThreadSemaphore.signal()
    }
    
    // MARK: - Public Methods
    
    // 启动
    public func start() {
        guard !isStarted else { return }
        isStarted = true
        taskQueue.async { [weak self] in
            // 加载挂起的上传任务
            self?._processPendingTasks()
            // 启动工作线程
            self?.workerThread.start()
            // 通知工作线程开始工作
            if !(self?.pendingTasks.isEmpty ?? true) {
                self?.workerThreadSemaphore.signal()
            }
        }
    }
    
    // 上传图片
    public func putData(data: Data, key: String, userId: String, extra: String?) throws -> Void {
        // 新建上传任务...
        let task: UploadTask = UploadTask(key: key, userId: userId, extra: extra)
        let cacheFilePath: String = try task.localCacheFilePath()
        let url: URL = URL(fileURLWithPath: cacheFilePath)
        try? FileManager.default.removeItem(at: url)
        try data.write(to: url)
        guard let _ = UploadTask.insert(with: task) else {
            _removeCacheImage(key)
            throw SPError.dbError
        }
        
        // 放入任务队列
        pendingTasksLock.lock()
        pendingTasks.append(task)
        pendingTasksLock.unlock()
        
        if !workerThread.isExecuting {
            workerThread.start()
        }
        workerThreadSemaphore.signal()
    }
}


// MARK: - Private Methods

extension UploadManager {
    
    // 删除缓存图片
    private func _removeCacheImage(_ key: String) {
        let filename: String = "\(key).\(photoExtension)"
        guard let path = StudyStateMachine.snapshotCachePath(filename) else {
            return
        }
        try? FileManager.default.removeItem(atPath: path)
    }
    
    // 处理挂起的上传任务
    private func _processPendingTasks() {
        // 处理上传成功的任务
        if let finishedTasks: [UploadTask] = UploadTask.all(by: [
            UploadTask.State.finished], userId: currentUserId), !finishedTasks.isEmpty {
            let keys: [String] = finishedTasks.map { $0.key }
            NotificationCenter.default.post(name: .uploadSuccess, object: keys)
        }
        
        // 处理上传未成功的任务
        guard let pendingTasks: [UploadTask] = UploadTask.all(by: [
            UploadTask.State.initial,
            UploadTask.State.failed,
            UploadTask.State.paused,
            UploadTask.State.uploading], userId: currentUserId) else {
                return
        }
        self.pendingTasksLock.lock()
        self.pendingTasks.removeAll(keepingCapacity: true)
        self.pendingTasks.append(contentsOf: pendingTasks)
        self.pendingTasksLock.unlock()
    }
    
    // 准备七牛的OSS信息
    private func _prepareOSS(_ key: String, completion: @escaping (_ error: Error?) -> Void) {
        if let oss = qiniuOSS, !(oss.image.token?.isExpired() ?? true) {
            completion(nil)
        } else {
            NetworkClient.instance.fetchOSSes(key: key, type: QiniuBucketType.photo, success: { [weak self] (data) in
                guard let data_ = data else {
                    completion(SPError.unexpectedServerResponse)
                    return
                }
                do {
                    let decoder: JSONDecoder = JSONDecoder()
                    let osses: [QiniuBuckets] = try decoder.decode([QiniuBuckets].self, from: data_)
                    self?.qiniuOSSLock.lock()
                    self?.qiniuOSS = osses.first
                    self?.qiniuOSSLock.unlock()
                    completion(nil)
                } catch {
                    completion(error)
                }
            }, failure: completion)
        }
    }
    
    // 工作队列线程
    @objc private func _workerThread(_ object: Any?) {
        repeat {
            workerThreadSemaphore.wait()
            
            let syncSemaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
            
            pendingTasksLock.lock()
            let copiedPendingTasks: [UploadTask] = pendingTasks
            pendingTasksLock.unlock()
            
            guard !canExitWorkerThread else {
                break
            }
            
            for task in copiedPendingTasks {
                guard let cacheFiePath = try? task.localCacheFilePath() else {
                    continue
                }
                let url = URL(fileURLWithPath: cacheFiePath)
                _prepareOSS(task.key) { [weak self] (error) in
                    guard error == nil, let oss = self?.qiniuOSS,
                        let token: String = oss.image.token?.token,
                        let data: Data = try? Data(contentsOf: url) else {
                        syncSemaphore.signal()
                        return
                    }
                    NetworkClient.instance.uploadImage(data: data, key: task.key, token: token, progress: { (progress) in
                        UploadTask.updateProgress(by: Database.PrimaryKey.custom(key: task.key), progress: progress)
                    }, completion: { [weak self] (error) in
                        if error == nil {
                            let qiniuURLStr: String = oss.image.domain + "/" + task.key
                            // 更新数据库任务状态
                            UploadTask.updateURL(by: Database.PrimaryKey.custom(key: task.key), url: qiniuURLStr)
                            UploadTask.updateState(by: Database.PrimaryKey.custom(key: task.key), state: UploadTask.State.finished)
                            
                            // 从队列中移除任务
                            self?.pendingTasksLock.lock()
                            self?.pendingTasks.removeAll { $0 == task }
                            self?.pendingTasksLock.unlock()
                            
                            // 删除缓存图片
                            self?._removeCacheImage(task.key)
                            
                            // 通知任务成功
                            NotificationCenter.default.post(name: .uploadSuccess, object: [task.key])
                        } else {
                            // 更新数据库任务状态
                            UploadTask.updateState(by: Database.PrimaryKey.custom(key: task.key), state: UploadTask.State.failed)
                            // 通知任务失败
                            NotificationCenter.default.post(name: .uploadFailure, object: [task.key])
                        }
                        syncSemaphore.signal()
                    })
                }
                syncSemaphore.wait()
                Thread.sleep(forTimeInterval: 1)
            }
        } while (!canExitWorkerThread)
    }
    
}
